<template>
    <NLayout class="w-full h-full layout">
        <NLayoutHeader class="w-full" bordered>
            <h1 class="text-center m-1">聊天室</h1>
        </NLayoutHeader>
        <NLayout has-sider>
            <NLayoutSider
                bordered class="p-2" show-trigger="arrow-circle" :collapsed-width="0" :width="300" :collapsed="!store.state.layout.sider.show"
                @update-collapsed="(v) => { store.commit('siderShowTransform', { show: !v }); }">
                <ChatTree @update:display-node="updateDisplayNode" @update:data="updateTreeData" @change="writeDataToLocalStorage()" :disabled="stopResponse.show"
                    :updateKey="chat.treeKey" :show-suffix="store.state.layout.sider.suffix.show" :version="store.state.version" v-model:data="localData.tree"
                    v-model:display-node="localData.displayNode"
                    @update:version=" (v) => { store.commit('update', { version: v }); } "
                ></ChatTree>
                <NDivider vertical class="h-full"></NDivider>
            </NLayoutSider>
            <NLayoutContent v-show="store.state.layout.content.show">
                <NLayoutContent >
                    <NRow>
                        <ChatLog id="chatLog" :disabled="chat.disabled" :theme="store.state.theme" @reload="reload" v-model:conversation="chat.conversation"
                            v-model:scrollToIndex="chat.scrollToIndex" :updateKey="chat.logKey" class="w-full overflow-auto n-layout-header" style="height:55dvh" 
                            @update:conversation=" (v) => { chat.conversation = v; writeDataToLocalStorage()} "
                        ></ChatLog>
                    </NRow>
                    <NRow :class="store.state.theme">
                        <div class="m-auto h-10">
                            <NButton v-show="stopResponse.show" @click="stopResponse.stop = true;deactivateStopButton()" class="w-full m-auto" type="warning">停止回复</NButton>
                        </div>
                    </NRow>
                </NLayoutContent>
                <NLayoutFooter>
                    <div class="w-full inline-flex">
                        <NBadge color="none" style="width: 80%" >
                            <NInput
                                :class="'w-full ring-1 '+{true:'ring-green-400',false:'ring-sky-400'}[chat.link.toString()] "
                                @keydown.ctrl.enter="confirm"
                                round
                                type="textarea"
                                :placeholder="{
                                    true:'Ctrl+Enter发送 Enter换行 右上角图标开启和关闭联网 联网功能已开启',
                                    false:'Ctrl+Enter发送 Enter换行 右上角图标开启和关闭联网 联网功能未开启'}[chat.link.toString()]"
                                v-model:value="chat.input"
                            >
                            </NInput>
                            <template #value>
                                <NButton text @click="linkStatusTransform" :type="({true:'primary',false:'info'}[chat.link.toString()] as Type)">
                                    <template #icon>
                                        <NIcon :size="20">
                                            <div v-html="getLinkIcon()"></div>
                                        </NIcon>
                                    </template>
                                </NButton>
                            </template>
                        </NBadge >
                        <div class="m-auto">
                            <NButton
                                type="primary"
                                @click="confirm"
                                :disabled="sendDisabled"
                            >
                                <template #icon
                                    ><NIcon :size="30"
                                        ><div v-html="SendIcon" /></NIcon
                                ></template>
                                发送
                            </NButton>
                            <br />
                            <NButton
                                type="info"
                                @click="exportJsonToFile(localData)"
                            >
                                <template #icon
                                    ><NIcon :size="30"
                                        ><div v-html="ExportIcon"></div></NIcon
                                ></template>
                                导出
                            </NButton>
                            <br />
                            <NUpload :on-before-upload="handleBeforeUpload" action="" :show-file-list="false" :format="['json']">
                                <NButton
                                    type="warning"
                                    @click="message.info('导入功能正在制作')"
                                >
                                    <template #icon
                                        ><NIcon :size="30"
                                            ><div
                                                v-html="ImportIcon"
                                            ></div></NIcon
                                    ></template>
                                    导入
                                </NButton>
                            </NUpload>
                        </div>
                    </div>
                </NLayoutFooter>
            </NLayoutContent>
        </NLayout>
    </NLayout>
</template>
<script lang="ts" setup>

import { reactive, ref, watch } from 'vue';
import {
    NLayout, NLayoutSider, NLayoutContent, NLayoutFooter, NRow, NCol,
    NInput, NButton, NButtonGroup, NDivider, NIcon, NUpload, NBadge, NLayoutHeader, useMessage, SelectOption} from 'naive-ui';
import { useStore } from 'vuex';
import ChatLog from "./components/ChatLog.vue"
import ChatTree from './components/ChatTree.vue';
import { fetchAnswer, fetchBingQuery } from "../../api/chatgpt"
import { fetchArxivQuery } from "@/api/arxiv"
import SendIcon from "@/assets/icons/send.svg"
import ImportIcon from "@/assets/icons/import.svg"
import ExportIcon from "@/assets/icons/export.svg"
import LinkIcon from "@/assets/icons/link.svg"
import DisconnectIcon from "@/assets/icons/disconnect.svg"
import { Type } from 'naive-ui/es/button/src/interface';
import { SettledFileInfo } from 'naive-ui/es/upload/src/interface';
import { result } from 'lodash';

const template = ['players/player', {
    name: 'title-case(name)',
    jerseyNumber: '@jerseyNumber',
    yearOfBirth: 'number(yearOfBirth)',
    isRetired: 'boolean(isRetired = "true")'
}]
// let jsonData = xmlJs.xml2js(xml,{compact:true})
// console.log(jsonData)
const message = useMessage()
interface Conversation{
    id:number,
    role:string,
    content:string,
    target:string, //gpt和me两个对象
    time:number,
    icon:string,
    loading:boolean
}

interface ChatTreeOption{
    label:string,
    key:string|number,
    isLeaf:boolean,
    data?:any,
    editable:boolean,
    children?:ChatTreeOption[]
}
interface LocalData {
    tree:ChatTreeOption[]
    displayNode:ChatTreeOption|null,
}

const store = useStore()
let sendDisabled = ref(false)
let stopResponse = reactive({
    show:false,
    stop:false
})

let chat = reactive({
    input:"",
    conversation:[]  as Conversation[],
    scrollToIndex:0 as number,
    logKey:0,
    treeKey:0,
    disabled:false,
    link:false
})
function handleBeforeUpload (data:{
    file: SettledFileInfo,
    fileList: SettledFileInfo[]}
    ) {
    const reader = new FileReader();
    reader.readAsText(data.file.file as File);
    reader.onload = () => {
        if(reader.result){
            try{
                let data = JSON.parse(reader.result as string) 
                localData = {...data}
                writeDataToLocalStorage()
                chat.treeKey = Number(!chat.treeKey)
                chat.logKey = Number(!chat.logKey)
                message.success("导入成功")
            }catch{
                message.success("导入失败")
            }
        }
    };
    return false;
}
function getLocalData(key:string="chatAnyWhereData"):LocalData{
    let localDataString = localStorage.getItem(key)
    let treeData:ChatTreeOption[] = [{
        label:"新会话",key:new Date().getTime().toString(),data:[],isLeaf:true,editable:false
    }]
    let localData:{[key:string]:any} = {
        tree:treeData,
        displayNode:treeData[0]
    }
    if(localDataString!=null){
        let jsonParse = JSON.parse(localDataString)
        for(let key of  Object.keys(localData)){
            if(jsonParse[key]==undefined){
                jsonParse[key] = localData[key]
            }
        }
        localData = jsonParse
    }
    if(localData.displayNode!=null){
        chat.conversation = localData.displayNode.data
    }
    else{
        chat.conversation = []
    }
    return localData as LocalData
}
function linkStatusTransform(){
    chat.link=!chat.link
    if(chat.link){message.info('开启联网，之后会话将不包含历史信息')}
    else{message.info('关闭联网，之后会话将包含历史信息')}
}
function getLinkIcon(){
    if(chat.link){return LinkIcon}else{return DisconnectIcon}
}
function setLocalData(data:LocalData,key:string="chatAnyWhereData"):void{
    localStorage.setItem(key,JSON.stringify(data))
}
function updateTreeData(v:ChatTreeOption[]){
    localData.tree=v;
}
function updateDisplayNode(v:ChatTreeOption|null){
    localData.displayNode = v;
    if(v==null){
        chat.conversation = []
    }
    else{
        chat.conversation = v.data
    }
}
let localData = reactive(getLocalData())
if(chat.conversation){
    {
        if(chat.conversation.length){

            chat.conversation[chat.conversation.length-1].loading = false
        }
    }
}

function findLeafNodes(nodes:ChatTreeOption[], result:ChatTreeOption[]) {
    for(let i=0;i<nodes.length;i++){
        let node = nodes[i]
        if (node.isLeaf) {
            result.push(node); // 如果当前节点是叶子节点，则把当前节点的信息存入result列表
        }
        else {
            if(node.children){
                for (let i = 0; i < node.children.length; i++) {
                    findLeafNodes(node.children, result); // 递归调用子节点，查找所有叶子节点
                }
            }
        }
    }
}
async function newConversationIfZeroTree(){
    let title = ""
    let result:ChatTreeOption[] = []
    findLeafNodes(localData.tree,result)
    if(result.length==0){
        if(chat.conversation[0]){
            title = chat.conversation[0].content
        }
        let displayNode:ChatTreeOption = {
            label:title,key:new Date().getTime().toString(),editable:false,isLeaf:true
        }
        localData.tree.push(displayNode)
        localData.displayNode = displayNode
    }
}
async function writeDataToLocalStorage(){
    if(localData.displayNode!=null){
        localData.displayNode.data = chat.conversation
        chat.logKey = Number(!chat.logKey)
    }
    else{
        let result:ChatTreeOption[] = []
        findLeafNodes(localData.tree,result)
        if(result.length>0){
            localData.displayNode = result[0]
            chat.conversation = localData.displayNode.data
        }
        else{
            }

        chat.treeKey = Number(!chat.treeKey)
        chat.logKey = Number(!chat.logKey)
    }
    setLocalData(localData)
}
async function exportJsonToFile(jsonData:any) {
  // 将 JSON 数据转换为字符串
  const jsonString = JSON.stringify(jsonData, null, 2);
  // 创建 Blob 对象
  const blob = new Blob([jsonString], { type: 'text/plain' });
  // 创建 URL
  const url = URL.createObjectURL(blob);
  // 创建 a 标签
  const link = document.createElement('a');
  link.href = url;
  link.download = 'data.json'; // 设置文件名
  document.body.appendChild(link);
  // 点击下载
  link.click();
  // 销毁 URL 和 a 标签
  URL.revokeObjectURL(url);
  document.body.removeChild(link);
}
function deactivateStopButton(){
    stopResponse.show = false
    stopResponse.stop = true
    sendDisabled.value = false
    chat.disabled = false
}
function activateStopButton(){
    stopResponse.show = true
    stopResponse.stop = false
    sendDisabled.value = true
    chat.disabled = true
}
async function getKeywords(conversation:Conversation[]):Promise<{ [key: string]: string}>{
    // 得到搜索关键词
    // Based on the chat history, 
    let textForGetKeyWord:string = `
    Today is ${new Date().toLocaleDateString()},
    please provide one or more keywords used to assist online searches in both Chinese and English , 
    excluding any additional information
    `
    let englishKeywords = ""
    let chineseKeywords = ""
    const chineseRegex = /[\u4e00-\u9fa5\d]+/g; // 匹配中文字符
    const englishRegex = /[a-zA-Z\d]+/g; //匹配英文字符
    let queryConversation = [{role:'user',content:textForGetKeyWord+conversation[conversation.length-1].content}]
    let newConversation = []
    for(let i=0;i<conversation.length;i++){
        newConversation.push(conversation[i])
    }
    let keywordsResponse = fetchAnswer(queryConversation,store.state.apiKey,store.state.version)
    if(keywordsResponse){
        let queryString =  (await(await keywordsResponse).text()).toLowerCase()
        console.log("keywords:",queryString)
        let keywords = ( (queryString)).replaceAll("keywords"," ").replaceAll("\""," ").replaceAll(".","")
        .replaceAll("搜索关键词：","").replaceAll("。"," ").replaceAll("，"," ").replaceAll(","," ").replaceAll("关键词：","").replace("中文"," ")
        .replace("chinese","").replace("english","")
        .replace("英文"," ").replaceAll("：","").replaceAll(":","")
        console.log("keywords process:",queryString)
        const chineseMatches = keywords.match(chineseRegex); // 获取所有的中文字符
        const englishMatches = keywords.match(englishRegex); // 获取所有的英文字符
        if(chineseMatches){
            chineseKeywords = chineseMatches?.join(" ")
        }
        if(englishMatches){
            englishKeywords = englishMatches?.join(" ")
        }
        }
        console.log("zh Keywords:",chineseKeywords,"en Keywords:",englishKeywords)
    return { zhCN: chineseKeywords, enUS: englishKeywords }
}
async function reload(index:number){
    activateStopButton()
    chat.scrollToIndex = index
    let conversation:Conversation[] = []
    for(let i=0;i<index;i++){
        conversation.push({...chat.conversation[i]})
    }
    if(chat.link){
            //从搜索引擎那里得到相关信息
            let queryAllData = ""
            console.log("conversation:",conversation)
            let keywordsDict = await getKeywords([conversation[conversation.length-1]])
            if(store.state.database.includes("bing")){
                chat.conversation[index].content = "正在搜索关键词："+keywordsDict.zhCN
                let bingResponse = await fetchBingQuery(keywordsDict.zhCN+" OR "+keywordsDict.enUS)
                let bingResult = await (bingResponse).text()
                queryAllData+=`bing ${bingResult}\n`
            }
            if(store.state.database.includes('arxiv')){
                let arxivResult = await fetchArxivQuery(keywordsDict.enUS)
                queryAllData+=`arxiv search results:\n${arxivResult}\n`
            }
            //输入搜索结果并得到回答
            chat.conversation[index].content = "正在提供搜索结果"
            let questionWithWebInfo = `
            ${queryAllData}
            Today is ${new Date().toLocaleDateString()}, 
            The above information was obtained from the Internet and can be used to answer the following message in Chinese, 
            each part of content must be followed by a referenced of the source url in markdown format:
            ${chat.conversation[index-1].content}`
            console.log("query:",queryAllData)
            let responseConversation = {
                id:chat.conversation[index].id+1,
                role:"assistant",
                content:"",
                target:'AI',
                time:new Date().getTime(),
                icon:"aislogo",
                loading:false
            }
            chat.conversation[index] = responseConversation
            conversation[conversation.length-1].content = questionWithWebInfo
            query([conversation[conversation.length-1]],chat.conversation[index])
        }
    else{
        await query(conversation,chat.conversation[index],index)
    }
    chat.scrollToIndex = index

}
async function query(conversation:Conversation[],targetResponse:Conversation,targetId:number|null=null){
    chat.scrollToIndex = chat.conversation.length-1
    chat.logKey = Number(!chat.logKey)
    activateStopButton()
    chat.disabled = true
    let id = conversation[conversation.length-1].id+1
    if(targetId!=null){
        id = targetId
    }
    let messageResponse = fetchAnswer(conversation,store.state.apiKey,store.state.version)
    if(messageResponse){
        await messageResponse.then((response)=>{
            targetResponse.content = ""
            if(response.body!=null){
                const reader = response.body.getReader();
                reader.read().then(function processText({ done, value }) {
                    if(done || stopResponse.stop){
                        newConversationIfZeroTree()
                        writeDataToLocalStorage()
                        deactivateStopButton()
                        return Promise.resolve(true);
                    }

                    let text = new TextDecoder("utf-8").decode(value);
                    targetResponse.content += text
                    chat.logKey = Number(!chat.logKey)
                    reader.read().then(processText);
                })
            }
        }).catch(()=>{
            targetResponse = {
                    id:id,
                    role:"assistant",
                    content:"连接失败",
                    target:'系统',
                    time:new Date().getTime(),
                    icon:"BrainCircuit20Regular",
                    loading:false
                }
            let targetid = chat.conversation.length-1
            if(targetId!=null){
                targetid = targetId
            }
            chat.conversation[targetid] = targetResponse
            deactivateStopButton()
        })
    }

}


async function confirm(){
    let id = 0
    if(!chat.conversation){
        chat.conversation = []
    }
    if(chat.conversation.length>0){
        id = chat.conversation[chat.conversation.length-1].id+1
    }
    if(chat.input!="" && sendDisabled.value==false){
        chat.conversation.push(
            {
                "id":id,
                role:"user",
                content:chat.input,
                target:'我',
                time:new Date().getTime(),
                icon:"UserOutlined",
                loading:false
            }
        );
        chat.scrollToIndex = chat.conversation.length-1
        id+=1
        chat.input=''
        sendDisabled.value = true
        stopResponse.show = true
        let initialConversation = {
                id:id,
                role:"assistant",
                content:"正在请求数据中······",
                target:'AI',
                time:new Date().getTime(),
                icon:"aislogo",
                loading:true

            }
        chat.conversation.push(initialConversation)
        await reload(chat.conversation.length-1)}
}

</script>
<style>
.n-layout-header,
.n-layout-footer {
    background: rgba(128, 128, 128, 0.15);
    padding: 24px;
}

.n-layout-sider {
    background: rgba(128, 128, 128, 0.1);
}

.n-layout-content {
    background: rgba(0, 0, 0, 0.01);
}

.light {
    background: rgba(128, 128, 128, 0.1);
}
</style>
